#pragma once
#include <vector>
#include<iostream>
#include <assert.h>
#include <stdarg.h>
#include "config.h"
using namespace std;
/** 
 * Implements one single transition rule for the CA.
 * Rule objects do not have an explicit size. The size of the 
 * feature vector tells the size. 
 * \pre When calling the non-default constructor, the size of
 * the vector parameter should match the size of NBMask to be used.
 */
template <typename T>
class Rule
{
public:
	Rule();

	Rule(const Rule<T>& other)
	{
		for (vector<T>::const_iterator i = other.features.begin();	
			i != other.features.end();
			++i)
		{
			features.push_back(*i);
		}
	};

	Rule(const vector<T>& nb, const T& lb)
	{
		for (vector<T>::const_iterator i = nb.begin(); i != nb.end(); ++i)
			features.push_back(*i);
		label = lb;
	};

	/** This constructor gets a variable number of plain arguments.
	 * Finish the list of arguments with ENDOFPARAMS.
	 * The first argument cannot be -1.
	 */
	Rule(const T feature1, ...)
	{
		va_list featureList; //declare an argument pointer to a variable arg list
		va_start(featureList, feature1); //initialize arg pointer using last known arg 
		T inputArg = feature1;
		while (inputArg != ENDOFPARAMS)
		{
			features.push_back(inputArg);
			inputArg = va_arg(featureList, T);
		}
		label = features.back();
		features.pop_back();
		va_end (featureList);
	}

	/** 
	 * Tells if the given vector is the same as the "if part" of this rule or not.
	 */
	bool applies (const vector<T>& nb) const
	{
		if (nb.size() != features.size())
			return false;
		for (int i = 0; i < nb.size(); ++i)
			if (nb[i] != features[i])
				return false;
		return true;
	}

	T getLabel (void) const
	{
		return label;
	}

	/** 
	 * Tells if the given rule conflicts this object or not.
	 * \return true if the given Rule object has the same IF part and different labels, false otherwise.
	 */
	bool conflicts (const Rule<T>& other) const
	{
		assert (features.size() == other.features.size() && "Two different sized rules cannot be compare for conflicts");
		if (!hasSameFeatures(other))
			return false;
		if (label == other.label)
			return false;
		return true;
	}

	Rule<T>& operator= (const Rule<T>& rside)
	{
		if (this == &rside)
			return *this;
		features.clear();
		for (vector<T>::const_iterator i = rside.features.begin(); i != rside.features.end(); ++i)
			features.push_back(*i);
		label = rside.label;
		return *this;
	}

	bool operator== (const Rule<T>& rside) const
	{
		if (label != rside.label)
			return false;
		if (features.size() != rside.features.size())
			return false;
		for (int i = 0; i < (int)features.size(); ++i)
			if (features[i] != rside.features[i])
				return false;
		return true;
	}

	friend ostream& operator<< (ostream& os, const Rule<T>& obj)
	{
		for (vector<T>::const_iterator i = obj.features.begin(); i != obj.features.end(); ++i)
			os << *i << ",";
		os << "\b -> " << obj.label;
		return os;
	}

private:
	vector<T> features;
	T label;
	bool hasSameFeatures (const Rule<T>& other) const
	{
		assert (features.size() == other.features.size() && "Two different sized rules cannot be compare for having same features");
		//NOTE: We might need to return false for different sized feature vectors. For now we don't compare them.
		for (int i = 0; i < (int) other.features.size(); ++i)
			if (other.features[i] != features[i])
				return false;
		return true;
	}
};